iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 12
0
Mobile Development

Android × CI/CD 如何用基本的MVVM專案實現 CI/CD 系列 第 12

Day12 Nested RecyclerView(番外篇)

  • 分享至 

  • xImage
  •  

關於這個番外篇呢
其實跟這次鐵人賽的主題關聯性較低
沒興趣的可以先行跳過

寫這篇的原因是快富堅了 只好先塞其他內容墊檔一下

今天要舉的例子是這個
https://ithelp.ithome.com.tw/upload/images/20190927/201202795BkOdNwOrt.jpg
(圖取自google play)

現在很多app的首頁都有類似這種嵌套式UI 你分明就是想拿上班的工作項目來混水摸魚
那這個需求其實可以用內建的RecyclerView來實現

今天會建立新的專案來實做類似的頁面

解決方案放在這
https://github.com/mars1120/NestedRecyclerViewDemo

先設定Dependencies
build.gradle

Dependencies{
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
}

首先先建立垂直的recyclerview

先從首頁的xml開始
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_base"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

</androidx.constraintlayout.widget.ConstraintLayout>

接著建立可重複添加的recyclerview
水平的遷套recyclerview之後也會添加在這xml之內

view_item_parent_recycler.xml

<androidx.cardview.widget.CardView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/card_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_margin="2dp"
    card_view:cardBackgroundColor="#fff"
    card_view:cardCornerRadius="5dp"

    card_view:cardElevation="4dp"
    card_view:cardUseCompatPadding="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_title"
            style="@style/Base.TextAppearance.AppCompat.Subhead"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignStart="@+id/rv_child"
            android:layout_alignParentTop="true"
            android:padding="20dp"
            android:text="I'm title"
            android:textColor="@color/colorAccent"
            android:textStyle="bold" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_child"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="30dp"
            android:orientation="horizontal"
            android:padding="20dp"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            tools:layout_editor_absoluteX="74dp" />

    </RelativeLayout>

</androidx.cardview.widget.CardView>

另一個類型的item
view_banner.xml

<androidx.cardview.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/card_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_margin="2dp"
    card_view:cardBackgroundColor="#fff"
    card_view:cardCornerRadius="5dp"

    card_view:cardElevation="4dp"
    card_view:cardUseCompatPadding="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:background="@color/cardview_dark_background"
            android:id="@+id/tv_banner"
            style="@style/Base.TextAppearance.AppCompat.Subhead"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignStart="@+id/rv_child"
            android:layout_alignParentTop="true"
            android:layout_alignParentEnd="true"
            android:gravity="center"
            android:padding="40dp"
            android:text="I'm Banner"
            android:textColor="@color/colorPrimary"
            android:textStyle="bold" />


    </RelativeLayout>

</androidx.cardview.widget.CardView>

ItemParentModel
用來存資料的數據類

ItemParentModel.kt

data class ItemParentModel (
    val title : String = ""
)

ItemParentAdapter
用來綁定與設定recyclerView用的

ItemParentAdapter.kt

class ItemParentAdapter(private val parents: List<ItemParentModel>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup,
                                    viewType: Int): RecyclerView.ViewHolder {

        when (viewType) {
            0 -> return BannerViewHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.view_banner, parent, false))
            else -> return ItemViewHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.view_item_parent_recycler, parent, false))
        }

    }

    override fun getItemCount(): Int {
        return parents.size+1
    }

    override fun getItemViewType(position: Int): Int {
        return if (0 == position) {
            TYPE_BANNER
        } else {
            TYPE_ITEM
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder,
                                  position: Int) {

        when(holder)
        {
            is BannerViewHolder -> {
                holder.textView.text = "hello"
            }
            is ItemViewHolder -> {
                holder.textView.text =  parents[position-1].title
            }
        }
    }

    private inner class BannerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView: TextView = itemView.tv_banner
    }

    private inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView: TextView = itemView.tv_title
    }

    companion object {
        private val TYPE_BANNER = 0
        private val TYPE_ITEM = 1
    }
}

最後是首頁
Mainactivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var recyclerView: RecyclerView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        recyclerView = rv_base
        recyclerView.apply {
            adapter = ItemParentAdapter(getParents())
        }
    }
    private fun getParents(): List<ItemParentModel> {
        val listOfFacility = mutableListOf<ItemParentModel>()
        var facilityModel = ItemParentModel("Sample 0")
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 1")
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 2")
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 3")
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 4")
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 5")
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 6")
        listOfFacility.add(facilityModel)
        return listOfFacility
    }
}

目前階段的畫面

https://ithelp.ithome.com.tw/upload/images/20190927/20120279YgQ1rZaKF3.png

接下來會調整ItemParentAdapter讓他再嵌入一個recyclerview

首先先來新增child xml
view_item_child_recycler.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/child_textView"
        android:layout_width="128dp"
        android:layout_height="37dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="1dp"
        android:background="@android:color/darker_gray"
        android:padding="10dp"
        android:text="TextView"
        android:textColor="@android:color/white"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@id/child_imageView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <ImageView
        android:id="@+id/child_imageView"
        android:layout_width="126dp"
        android:layout_height="189dp"
        android:layout_marginBottom="38dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="42dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1.0"
        app:srcCompat="@drawable/ic_attach_money_128dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

其中@drawable/ic_attach_money_128dp可新增矢量圖或用其他圖源代替

新增child資料源
ChildModel.kt

data class ChildModel(
    val image: Int = -1,
    val title: String = ""
)

ChildAdapter.kt

class ChildAdapter(private val children: List<ChildModel>) :
    RecyclerView.Adapter<ChildAdapter.ViewHolder>() {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ViewHolder {

        val v = LayoutInflater.from(parent.context)
            .inflate(R.layout.view_item_child_recycler, parent, false)
        return ViewHolder(v)
    }

    override fun getItemCount(): Int {
        return children.size
    }

    override fun onBindViewHolder(
        holder: ViewHolder,
        position: Int
    ) {
        val child = children[position]
        holder.imageView.setImageResource(child.image)
        holder.textView.text = child.title
    }


    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        val textView: TextView = itemView.child_textView
        val imageView: ImageView = itemView.child_imageView

    }
}

接著要回頭修改ItemParentAdapter 串接child RecyclerView

ItemParentAdapter.kt

class ItemParentAdapter( ...
...
 private val viewPool = RecyclerView.RecycledViewPool()
   override fun onBindViewHolder() {
        when(holder)
        {
            is ItemViewHolder -> {
                holder.textView.text =  parents[position-1].title

                holder.recyclerView.apply {
                    adapter = ChildAdapter(parents[position-1].children)
                    setRecycledViewPool(viewPool)
                }
            }
        }
    }
    
 private inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView: TextView = itemView.tv_title
        val recyclerView : RecyclerView = itemView.rv_child
    }

父物件的資料源也要修改
data class ItemParentModel (
val title : String = "",
val children : List
)

最後是調整後的首頁
MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var recyclerView: RecyclerView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        recyclerView = rv_base
        recyclerView.apply {
            adapter = HomeItemParentAdapter(getParents())
        }
    }

    private fun getParents(): List<ItemParentModel> {
        val listOfFacility = mutableListOf<ItemParentModel>()
        var facilityModel = ItemParentModel("Sample 0", ChildDataFactory.getChildren(5))
        listOfFacility.add(facilityModel)

        facilityModel = ItemParentModel("Sample 1", ChildDataFactory.getChildren(5))
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 2", ChildDataFactory.getChildren(5))
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 3", ChildDataFactory.getChildren(5))
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 4", ChildDataFactory.getChildren(5))
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 5", ChildDataFactory.getChildren(5))
        listOfFacility.add(facilityModel)
        facilityModel = ItemParentModel("Sample 6", ChildDataFactory.getChildren(5))
        listOfFacility.add(facilityModel)

        return listOfFacility
    }

    object ChildDataFactory {
        private val random = Random()
        private val titles = arrayListOf("title0", "title1", "title2", "title3", "title4")
        private fun randomTitle(): String {
            val index = random.nextInt(titles.size)
            return titles[index]
        }
        private fun randomImage(): Int {
            return R.drawable.ic_attach_money_128dp
        }
        fun getChildren(count: Int): List<ChildModel> {
            val children = mutableListOf<ChildModel>()
            repeat(count) {
                val child = ChildModel(randomImage(), randomTitle())
                children.add(child)
            }
            return children
        }
    }
}

搞定

成品畫面
https://ithelp.ithome.com.tw/upload/images/20190927/20120279QWnrVoM2qy.png

最後再貼一次solution連結

https://github.com/mars1120/NestedRecyclerViewDemo

之後可能會拿本專案當基底做一些其他demo


上一篇
Day11 unit tests 實戰
下一篇
Day13 MVVM專案-2 Simple Fragment -1
系列文
Android × CI/CD 如何用基本的MVVM專案實現 CI/CD 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言